#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include "ca_section.h"
#include "ca_types.h"
#include "descrambler.h"
#include "ca_manager.h"
#include "eloop.h"

#define errorinfo(name)  printf("%s failed: errno=%d error=%s\n", #name, errno, strerror(errno))
#define dbgprintf(...) printf(__VA_ARGS__)

#define ELOOP_THREAD
#define MAX_CA_PID  8

typedef struct _cas_manager cas_manager_t;
typedef struct _section_data  section_data_t;
struct _cas_manager
{
  eloop_t   *eloop;
  desctx_t  *desctx;

  pid_channel_t  *pmt_channel;
  pid_channel_t  *cat_channel;

  pid_channel_t  *ecm_channel[MAX_CA_PID];
  short           ecm_pid[MAX_CA_PID];
  uint32_t        ecm_type[MAX_CA_PID];
  int             ecm_chcount;

  pid_channel_t  *emm_channel[MAX_CA_PID];
  short           emm_pid[MAX_CA_PID];
  int             emm_chcount;

  ca_module_t *module;
};

struct _section_data
{
  pid_channel_t    *channel;
  uint8_t          *section;
  uint32_t          length;

  cas_manager_t    *cas_manager;
};

enum {
  CAT_TABLE_ID = 0x01,
  PMT_TABLE_ID = 0x02,
  ECM_TABLE_ID0 = 0x80,
  ECM_TABLE_ID1 = 0x81,
};

enum {
  CA_DESCRIPTOR  = 0x09,
};

static void* ca_worker_thread(void *param);
static void  do_start_descrambling(eloop_t *eloop, void *data);
static void  do_stop_descrambling(eloop_t *eloop, void *data);
static void  do_update_descrambling(eloop_t *eloop, void *data);

static pid_channel_t* cas_request_pmt(cas_manager_t *mgr);
static pid_channel_t* cas_request_cat(cas_manager_t *mgr);
static void cas_section_cb(pid_channel_t *channel, uint8_t *section, uint32_t len, void *data);
static void section_handler(eloop_t *eloop, void *data);

static int cas_parse_cat(cas_manager_t *mgr, uint8_t *section);
static int cas_parse_pmt(cas_manager_t *mgr, uint8_t *section);

static void cas_parse_emm(cas_manager_t *mgr, pid_channel_t *ch, uint8_t *section);
static void cas_parse_ecm(cas_manager_t *mgr, pid_channel_t *ch, uint8_t *section);

static void cas_request_emm(cas_manager_t *mgr);
static void cas_request_ecm(cas_manager_t *mgr);

static cas_manager_t *g_cas_mgr = NULL;

void
cas_manager_init(void)
{
  int ret;
  pthread_attr_t attr;
  pthread_t thca;

  assert(g_cas_mgr == NULL);
  g_cas_mgr = calloc(1, sizeof(*g_cas_mgr));
  if (!g_cas_mgr)
    goto fail0;
  g_cas_mgr->module = ca_module_get();
  if (!g_cas_mgr->module)
    goto fail1;
  g_cas_mgr->module->module_init();

  g_cas_mgr->eloop = eloop_init();
  if (!g_cas_mgr->eloop)
    goto fail2;
  ret = pthread_attr_init(&attr);
  if (ret)
	goto fail3;
  pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  ret = pthread_create(&thca, &attr, ca_worker_thread, g_cas_mgr);
  pthread_attr_destroy(&attr);
  if (!ret)
    return;
  errorinfo(pthread_create);
fail3:
  errorinfo(pthread_attr_init);
fail2:
  //cas_manager_unload_module(g_cas_mgr);
fail1:
  free(g_cas_mgr);
  g_cas_mgr = NULL;
fail0:
  errorinfo(calloc);
}

void
start_descrambling(desctx_t *ctx)
{
  desctx_t *c = desctx_dup(ctx);
  dbgprintf("do_start_descrambling ...\n");
  eloop_add_run(g_cas_mgr->eloop, do_start_descrambling, c);
}

void
stop_descrambling(void)
{
  eloop_add_run(g_cas_mgr->eloop, do_stop_descrambling, g_cas_mgr);
}

void
update_descrambling(desctx_t *ctx)
{
  desctx_t *c = desctx_dup(ctx);
  eloop_add_run(g_cas_mgr->eloop, do_update_descrambling, c);
}

void
post_descrambling_event(ca_module_t *module, ca_event_t *event)
{
}

const desctx_t*
get_descrambler_context(void)
{
  return g_cas_mgr->desctx;
}

static void*
ca_worker_thread(void *param)
{
  cas_manager_t *casmgr = (cas_manager_t *)param;

  eloop_run(casmgr->eloop);

  return NULL;
}

static void ELOOP_THREAD
do_start_descrambling(eloop_t *eloop, void *data)
{
  int16_t pids[3];
  int     types[3];

  desctx_t *desctx = (desctx_t *)data;
  if (g_cas_mgr->desctx)
    do_stop_descrambling(eloop, g_cas_mgr);

  g_cas_mgr->desctx = desctx;

  pids[0] = desctx->vidpid;
  types[0] = VIDEO_STREAM;
  pids[1] = desctx->audpid;
  types[1] = AUDIO_STREAM;
  descrambler_set_streampids(pids, types, 2);
  dbgprintf("starting descrambling request pmt and cat....\n");
  cas_request_pmt(g_cas_mgr);
  cas_request_cat(g_cas_mgr); 
}

static void ELOOP_THREAD
do_stop_descrambling(eloop_t *eloop, void *data)
{
  int i;
  cas_manager_t *mgr = (cas_manager_t *)data;
  if (!mgr->desctx) return;

  descrambler_clear_streampids();
  if (mgr->pmt_channel)
    pid_channel_free(mgr->pmt_channel);
  if (mgr->cat_channel)
    pid_channel_free(mgr->cat_channel);

  for (i=0; i < mgr->ecm_chcount; i++)
  {
    if (mgr->ecm_channel[i])
	{
      pid_channel_free(mgr->ecm_channel[i]);
	  mgr->ecm_channel[i] = NULL;
	}
  }
  mgr->ecm_chcount = 0;
  for (i=0; i< mgr->emm_chcount; i++)
  {
    if (mgr->emm_channel[i])
	{
      pid_channel_free(mgr->emm_channel[i]);
	  mgr->emm_channel[i] = NULL;
	}
  }
  mgr->emm_chcount = 0;

  desctx_free(mgr->desctx);
  mgr->desctx = NULL;
}

static void ELOOP_THREAD
do_update_descrambling(eloop_t *eloop, void *data)
{
}

static pid_channel_t* ELOOP_THREAD
cas_request_pmt(cas_manager_t *mgr)
{
  uint8_t match[SECTION_FILTER_SIZE];
  uint8_t mask[SECTION_FILTER_SIZE];
  uint16_t progno;

  memset(match, 0, sizeof(match));
  memset(mask, 0, sizeof(mask));
  progno = mgr->desctx->progno;

  match[0] = PMT_TABLE_ID;//pmt table id
  mask[0] = 0xff;
  match[1] = (uint8_t)((progno >> 8) & 0xff);
  mask[1] = 0xff;
  match[2] = (uint8_t)(progno & 0xff);
  mask[2] = 0xff;

  mgr->pmt_channel = pid_channel_alloc(mgr->desctx->pmtpid, cas_section_cb, mgr, -1);
  assert(mgr->pmt_channel != NULL);
  pid_channel_add_section_filter(mgr->pmt_channel, match, mask);
  pid_channel_start(mgr->pmt_channel);

  return mgr->pmt_channel;
}

static pid_channel_t* ELOOP_THREAD
cas_request_cat(cas_manager_t *mgr)
{
  uint8_t match[SECTION_FILTER_SIZE];
  uint8_t mask[SECTION_FILTER_SIZE];

  memset(match, 0, sizeof(match));
  memset(mask, 0, sizeof(mask));

  match[0] = CAT_TABLE_ID;//cat table id
  mask[0] = 0xff;

  mgr->cat_channel = pid_channel_alloc(0x01/*cat pid*/, cas_section_cb, mgr, -1);
  assert(mgr->cat_channel != NULL);
  pid_channel_add_section_filter(mgr->cat_channel, match, mask);
  pid_channel_start(mgr->cat_channel);

  return mgr->cat_channel;
}

static void
cas_section_cb(pid_channel_t *channel, uint8_t *section, uint32_t len, void *data)
{
  cas_manager_t *mgr;
  section_data_t *d;
  if (!section || len == 0)
    return;
  mgr = (cas_manager_t *)data;

  d = malloc(sizeof(*d));
  assert(d != NULL);
  d->section = malloc(len);
  assert(d->section);

  d->channel = channel;
  d->length = len;
  d->cas_manager = mgr;
  memcpy(d->section, section, len);

  eloop_add_run(mgr->eloop, section_handler, d);
}

static void ELOOP_THREAD
section_handler(eloop_t *eloop, void *data)
{
  section_data_t *d = (section_data_t *)data;
  cas_manager_t *mgr = d->cas_manager;
  uint8_t *section = d->section;
  uint8_t table_id = section[0];
  int ret;

  if (table_id == CAT_TABLE_ID)
  {
    if (mgr->cat_channel)
	{
      assert(d->channel == mgr->cat_channel);
	  mgr->cat_channel = NULL;
      pid_channel_free(d->channel);
      dbgprintf("cat arrived\n");
      ret = cas_parse_cat(mgr, section);
	  if (ret)
	    cas_request_emm(mgr);
	}
  }
  else if (table_id == PMT_TABLE_ID)
  {
    if (mgr->pmt_channel)
	{
      assert(d->channel == mgr->pmt_channel);
	  mgr->pmt_channel = NULL;
	  pid_channel_free(d->channel);
      dbgprintf("pmt arrvied\n");
      ret = cas_parse_pmt(mgr, section);
	  if (ret)
	    cas_request_ecm(mgr);
	}
  }
  else if (table_id == ECM_TABLE_ID0 || table_id == ECM_TABLE_ID1)
  {
    cas_parse_ecm(mgr, d->channel, section);
  }
  else if ((table_id & 0xf0) == 0x80)
  {
    cas_parse_emm(mgr, d->channel, section);
  }

  free(d->section);
  free(d);
}

static int ELOOP_THREAD
cas_parse_cat(cas_manager_t *mgr, uint8_t *section)
{
  ca_module_t *module = mgr->module;
  int  ca_system_id = module->system_id;

  if (module->on_cat_arrived)
  {
    module->on_cat_arrived(section);
	return 0;
  }
  else
  {
    uint8_t *p = section;
	int parsedlen, descriptorlen;
	int cur_system_id;
	short emmpid;
    int len = ((p[1] & 0x0f) << 8) + p[2];
	p += 8;
	len -= 9;
	parsedlen = 0;

    if (mgr->emm_chcount > 0)
	  return 0;

	while(parsedlen < len)
	{
      descriptorlen = p[1];
      cur_system_id = (p[2] << 8) + p[3];
	  emmpid = ((p[4] & 0x1f) << 8) + p[5];

	  if (p[0] == CA_DESCRIPTOR)
	    dbgprintf("emmpid=%04x ca_system_id=%04x\n", emmpid, cur_system_id);
	  if ((p[0] == CA_DESCRIPTOR) && (cur_system_id == ca_system_id))
	  {
	    if (mgr->emm_chcount >= MAX_CA_PID)
		  return 1;
        
		mgr->emm_pid[mgr->emm_chcount] = emmpid;
		mgr->emm_chcount += 1;
	  }
	  parsedlen += (descriptorlen + 2);
	  p += (descriptorlen + 2);
	}
  }
  dbgprintf("emm_chcount=%d for ca_system_id=%04x\n", mgr->emm_chcount, ca_system_id);
  return 1;
}

static int ELOOP_THREAD
cas_parse_pmt(cas_manager_t *mgr, uint8_t *section)
{
  ca_module_t *module = mgr->module;
  int  ca_system_id = module->system_id;

  if (module->on_pmt_arrived)
  {
    module->on_pmt_arrived(section);
	return 0;
  }
  else
  {
    uint8_t *p = section;
	int length = ((p[1] & 0x0f) << 8) + p[2];
	int proglen = ((p[10] & 0x0f) << 8) + p[11];
	int parsedlen = 0;
	int eslen_total = length - 9 - proglen - 4;
    int descriptorlen;
    int cur_system_id;
    short ecmpid;

	p += 12;

    if (mgr->ecm_chcount > 0)
	  return 0;

	while (parsedlen < proglen)
	{
	  descriptorlen = p[1];
      cur_system_id = (p[2] << 8) + p[3];
	  ecmpid = ((p[4] & 0x1f) << 8) + p[5];

	  if (p[0] == CA_DESCRIPTOR)
	    dbgprintf("[ts]ecmpid=%04x ca_system_id=%04x\n", ecmpid, cur_system_id);
	  if ((p[0] == CA_DESCRIPTOR) && (cur_system_id == ca_system_id))
	  {
	    if (mgr->ecm_chcount >= MAX_CA_PID)
		  return 1;
	    mgr->ecm_pid[mgr->ecm_chcount] = ecmpid;
		mgr->ecm_type[mgr->ecm_chcount] = (ECM_VIDEO | ECM_AUDIO | ECM_DATA);
		mgr->ecm_chcount += 1;
	  }

	  parsedlen += (descriptorlen + 2);
	  p += (descriptorlen + 2);
	}

	parsedlen = 0;
	while (parsedlen < eslen_total)
	{
	  uint8_t stream_type = p[0];
	  int eslen = ((p[3] & 0x0f) << 8) + p[4];
	  int esparsedlen = 0;
      uint8_t *tmp = p + 5;

      while (esparsedlen < eslen)
	  {
	    descriptorlen = tmp[1];
		cur_system_id = (tmp[2] << 8) + tmp[3];
		ecmpid = ((tmp[4] & 0x1f) << 8) + tmp[5];

	    if (p[0] == CA_DESCRIPTOR)
	      dbgprintf("[es]ecmpid=%04x ca_system_id=%04x\n", ecmpid, cur_system_id);
		if ((tmp[0] == CA_DESCRIPTOR) && (cur_system_id == ca_system_id))
		{
	      if (mgr->ecm_chcount >= MAX_CA_PID)
		    return 1;
		  mgr->ecm_pid[mgr->ecm_chcount] = ecmpid;
		  if (stream_type == 0x01 || stream_type == 0x02 || stream_type == 0x1b)
		    mgr->ecm_type[mgr->ecm_chcount] = ECM_VIDEO;
		  else if (stream_type == 0x03 || stream_type == 0x04 || stream_type == 0x06)
		    mgr->ecm_type[mgr->ecm_chcount] = ECM_AUDIO;
		  else
		    mgr->ecm_type[mgr->ecm_chcount] = ECM_DATA;
		  mgr->ecm_chcount += 1;
		}
		esparsedlen += (descriptorlen + 2);
		tmp += (descriptorlen + 2);
	  }
	  parsedlen += (eslen + 5);
	  p += (eslen + 5);
	}
    dbgprintf("ecm_chcount=%d for ca_system_id=%04x\n", mgr->ecm_chcount, ca_system_id);
  }

  return 1;
}

static void
cas_request_emm(cas_manager_t *mgr)
{
  ca_module_t *module = mgr->module;
  uint8_t **match, **mask;
  int count = 0;
  int i, k;

  if (module->get_emm_filter_match_mask)
    module->get_emm_filter_match_mask(&match, &mask, &count);
  if (count == 0)
    return;

  for (i=0; i<mgr->emm_chcount; i++)
  {
    mgr->emm_channel[i] = pid_channel_alloc(mgr->emm_pid[i], cas_section_cb, mgr, -1);
    for (k=0; k<count; k++)
    {
      pid_channel_add_section_filter(mgr->emm_channel[i], match[k], mask[k]);
    }
	pid_channel_start(mgr->emm_channel[i]);
  }
}

static void
cas_request_ecm(cas_manager_t *mgr)
{
  uint8_t match[SECTION_FILTER_SIZE];
  uint8_t mask[SECTION_FILTER_SIZE];
  int i;

  memset(match, 0, sizeof(match));
  memset(mask, 0, sizeof(mask));

  match[0] = ECM_TABLE_ID0;
  mask[0] = 0xff;

  for (i=0; i<mgr->ecm_chcount; i++)
  {
    mgr->ecm_channel[i] = pid_channel_alloc(mgr->ecm_pid[i], cas_section_cb, mgr, -1);
	assert(mgr->ecm_channel[i] != NULL);
	pid_channel_add_section_filter(mgr->ecm_channel[i], match, mask);
	pid_channel_start(mgr->ecm_channel[i]);
  }
}

static void
cas_parse_emm(cas_manager_t *mgr, pid_channel_t *ch, uint8_t *section)
{
  ca_module_t *module = mgr->module;
  if (module->on_emm_arrived)
    module->on_emm_arrived(section);
}

static void
cas_parse_ecm(cas_manager_t *mgr, pid_channel_t *ch, uint8_t *section)
{
  int i;
  int t = -1;
  uint8_t id = section[0];
  uint8_t match[SECTION_FILTER_SIZE];
  uint8_t mask[SECTION_FILTER_SIZE];

  memset(match, 0, sizeof(match));
  memset(mask, 0, sizeof(mask));

  ca_module_t *module = mgr->module;

  for (i=0; i<mgr->ecm_chcount; i++)
  {
    if (mgr->ecm_channel[i] == ch)
	{
	  t = i;
	  break;
	}
  }
  assert((mgr->ecm_channel[t] != NULL) && (t != -1));

  if (module->on_ecm_arrived)
    module->on_ecm_arrived(section, mgr->ecm_type[t], mgr->ecm_pid[t]);

  pid_channel_free(mgr->ecm_channel[t]);
  //request next ecm
  if (id == ECM_TABLE_ID0)
    id = ECM_TABLE_ID1;
  else
    id = ECM_TABLE_ID0;

  match[0] = id;
  mask[0] = 0xff;
  mgr->ecm_channel[t] = pid_channel_alloc(mgr->ecm_pid[t], cas_section_cb, mgr, -1);
  pid_channel_add_section_filter(mgr->ecm_channel[t], match, mask);
  pid_channel_start(mgr->ecm_channel[t]);
}

